/*********************************************************************************
 *      Copyright:  (C) 2025 lijiahui<2199250859@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  net_client.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(21/04/25)
 *         Author:  lijiahui <2199250859@qq.com>
 *      ChangeLog:  1, Release initial version on "21/04/25 19:08:44"
 *                 
 ********************************************************************************/

#include "net_client.h"


int socket_client_init(sock_t *sock, char *serv_ip, int port)
{
	if(!serv_ip || !port)
	{
		log_warn("get server message wrong\n");
		return -1;
	}

	sock->port = port;
	sock->fd = -1;//初始时未连接，套接字无效
	sock->is_connected = 0;//标记为未连接状态

	strncpy(sock->servip, serv_ip, sizeof(sock->servip) - 1);

	return 0;
}

int socket_close(sock_t *sock)
{
	if(!sock)
	{
		return -1;
	}
	
	if(sock->fd >= 0)
	{
		close(sock->fd);
		sock->fd = -1;
		sock->is_connected = 0;
	}

	return 0;
}

int socket_connect(sock_t *sock)
{
    struct addrinfo     hints, *res = NULL, *rp;
    int                 rv = -1;
    int                 connected = 0;
    char                port_str[8];
    int                 optval = 1;
    socklen_t           optlen = sizeof(optval);

    if (!sock || !sock->servip[0] || sock->port <= 0) {
        log_error("Invalid parameters\n");
        return -1;
    }

    //关闭旧连接（防止重复调用泄漏fd)
    if (sock->fd >= 0) {
        close(sock->fd);
        sock->fd = -1;
        sock->is_connected = 0;
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;      // IPv4
    hints.ai_socktype = SOCK_STREAM; // TCP
    hints.ai_protocol = IPPROTO_TCP; // TCP协议

    snprintf(port_str, sizeof(port_str), "%d", sock->port);

    //解析域名/IP 
    rv = getaddrinfo(sock->servip, port_str, &hints, &res);
    if (rv != 0) {
        log_error("getaddrinfo failed: %s\n", gai_strerror(rv));
        return -2;
    }

    //尝试所有解析到的地址
    for (rp = res; rp != NULL; rp = rp->ai_next) {
        //每次尝试都创建新socket（避免资源泄露，socket与地址是一对一的）
        sock->fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sock->fd < 0) {
            log_debug("Create socket failed: %s\n", strerror(errno));
            continue;
        }

        //设置SO_REUSEADDR（快速重用地址）
        if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen) < 0) {
            log_debug("setsockopt SO_REUSEADDR failed: %s\n", strerror(errno));
            close(sock->fd);
            sock->fd = -1;
            continue;
        }

        //尝试连接
        if (connect(sock->fd, rp->ai_addr, rp->ai_addrlen) == 0) {
            connected = 1;
            break; // 成功则退出循环
        }

        //连接失败处理
        log_debug("Connect to [%s:%d] failed: %s\n", 
                 sock->servip, sock->port, strerror(errno));
        close(sock->fd);
        sock->fd = -1;
    }

    freeaddrinfo(res);

    //检查连接结果
    if (!connected) {
        log_error("All connection attempts failed for [%s:%d]\n", 
                 sock->servip, sock->port);
        return -3;
    }

    sock->is_connected = 1;
    log_info("Successfully connected to [%s:%d]\n", sock->servip, sock->port);
    return 0;
}


int socket_check(sock_t *sock)//检查客户端连接状态
{
	struct tcp_info info;
	socklen_t		len = sizeof(info);

	if(!sock || sock->fd<0)//检查指针有效性和文件描述符有效性，无效立即更新connect状态
	{
		sock->is_connected = 0;
		return 0;
	}

	if(getsockopt(sock->fd, IPPROTO_TCP, TCP_INFO, &info, &len) != 0)//获取TCP连接的详细状态
	{
		sock->is_connected =0;
		log_error("Can not get TCP state:%s\n", strerror(errno));
		
		if(sock->fd >0)
		{
			close(sock->fd);
			sock->fd = -1;
		}
		
		return 0;
	}

	if(info.tcpi_state == 1)//连接已建立
	{
		sock->is_connected = 1;
		//log_info("The connection is establish\n");
	}
	else//连接未建立
	{
		sock->is_connected = 0;
		
		if(sock->fd > 0)
		{
			close(sock->fd);
			sock->fd = -1;
		}
		
		log_info("The connection has been dropped\n");
	}
	return 1;

}
 
